#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>
#include <pthread.h>
#include <math.h>
#include <sys/time.h>
#include <time.h>


struct timeval inicio,final;

/* Variaveis para calculo do valor aproximado de PI */
mpf_t pi;
mpf_t pontosTotal;
mpf_t pontosValidos;

unsigned int ptosTotalParalelo=0;
unsigned int ptosValidosParalelo=0;

int numPontos;
int numThreads;

pthread_t *threads;
pthread_mutex_t mutex;

/* Destroi as variaveis da biblioteca GMP utilizadas para calcular o valor de PI */
void destrutorMonteCarloParalelo(){
    mpf_clear(pi);
    mpf_clear(pontosTotal);
    mpf_clear(pontosValidos);

}

/* Define a precisão do ponto flutuante da biblioteca GMP e inicializa as variaveis */
void iniciaMonteCarloParalelo(){
    mpf_set_default_prec (340000000);
    mpf_init(pi);
    mpf_init(pontosTotal);
    mpf_init(pontosValidos);

}

/* Calculo do valor aproximado do PI. Foi utilizado a forma mais simples para o calculo PI = 4 * (PtosDentroDaCircunferencia/PtosTotais) */
void calculaPiMonteCarloParalelo(){
    mpf_set_ui(pontosValidos, ptosValidosParalelo);
    mpf_set_ui(pontosTotal, ptosTotalParalelo);
    mpf_div(pi, pontosValidos, pontosTotal);
    mpf_mul_ui(pi, pi, 4);
}

/* Seleciona pontos aleatorios controla as Threads e o acesso a parte critica do programa. Cada thread executa essa parte do algoritmo de Monte
Carlo.
*/
void *calculaMonteCarloParalelo(){
    mpf_t tempEstaCirc;
    mpf_t x;
    mpf_t y;

    int i;

    unsigned int auxPtosTotal=0;
    unsigned int auxPtosValidos=0;

    mpf_init2(tempEstaCirc, 1000);
    mpf_init2(x, 1000);
    mpf_init2(y, 1000);

    gmp_randstate_t state;

    /* funcao da GMP para numeros randomicos */
    gmp_randinit_mt(state);
    gmp_randseed_ui(state, time(NULL));

    for(i=0; i<numPontos/numThreads; i++){
        /* funcao da GMP que seleciona numeros aleatorios entre 0 e 1 */
        mpf_urandomb(x, state, 100);
        mpf_urandomb(y, state, 100);
        mpf_mul(x, x, x);
        mpf_mul(y, y, y);
        mpf_add(tempEstaCirc, x, y);
        /* verifica se o ponto esta dentro da circunferencia */
        if(mpf_cmp_ui(tempEstaCirc, 1) <= 0){
            auxPtosValidos++;
        }
        auxPtosTotal++;
    }
    mpf_clear(tempEstaCirc);
    mpf_clear(x);
    mpf_clear(y);
    /* regiao critica, apenas uma thread de cada vez pode alterar o valor das variaveis do numero de pontos dentro da circunferencia e do numero
    total de pontos
    */
    pthread_mutex_lock(&mutex);
    ptosValidosParalelo += auxPtosValidos;
    ptosTotalParalelo += auxPtosTotal;
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

/* Calcula o tempo e chama as funcoes para inicializar as variaveis, destrui-las e para calcular o valor aproximado do PI e faz a selecao
aleatoria dos pontos. Deve ser passado o numero de pontos a ser selecionado e o numero de Threads.
*/
void MonteCarloParalelo(int numPtos, int numThds){
    FILE *MonteCarloSeq;
    MonteCarloSeq = fopen("monteCarloParalelo.txt", "w+");
    numPontos = numPtos;
    numThreads = numThds;

    int tempo1;
    int tempo2;
    int i;

    /* marca tempo inicial de execucao */
    gettimeofday(&inicio, NULL);

    /* inicia as variaveis */
    iniciaMonteCarloParalelo();

    /* threads criadas dinamicamente */
    threads = (pthread_t*)malloc(sizeof(pthread_t) * numThreads);

    /* criacao das threads */
    for(i=0;i<numThreads;i++){
        pthread_create(&(threads)[i],NULL,calculaMonteCarloParalelo,NULL);
    }
    /* inicializacao das threads */
    for(i=0;i<numThreads;i++){
        pthread_join(threads[i],NULL);
    }

    /* calcula o valor aproximado de PI */
    calculaPiMonteCarloParalelo();

    /* marca tempo final da execucao */
    gettimeofday(&final, NULL);

    /* mostra valor aproximado de PI */
    gmp_printf("pi calculado = %.10000000Ff\n",pi);
    mpf_out_str(MonteCarloSeq,10,10000000,pi);

    /* calcula tempo em milisegundos e segundos */
    tempo1 = final.tv_usec - inicio.tv_usec;
	tempo2 = final.tv_sec - inicio.tv_sec;

	/* mostra informacoes adicionais */
	printf("Contador de iterações %i\n\n", ptosTotalParalelo);
    printf("Tempo em %i milisegundos\n",tempo1*(-1));
    printf("Tempo em %i segundos\n",tempo2);
    printf("Pontos Dentro: %d       Pontos Total: %d\n", ptosValidosParalelo, ptosTotalParalelo);
    printf("Quantidade Threads: %d         Pontos para cada Thread: %d\n\n\n", numThreads, (numPontos/numThreads));

    /* fecha arquivo com o valor aproximado de PI */
    fclose(MonteCarloSeq);
    /* destroi as variaveis da GMP */
    destrutorMonteCarloParalelo();
    /* desaloca as Threads criadas */
    free(threads);

}
